package org.noote.libs.sogle;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

import android.opengl.Matrix;
import android.util.FloatMath;

public class Mesh extends Object3D {

	protected int numOfIndices = -1;

	protected FloatBuffer verticesBuffer = null;
	protected FloatBuffer normalesBuffer = null;
	protected FloatBuffer colorBuffer = null;
	protected ShortBuffer indicesBuffer = null;

	public float[] rgba = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };

	protected boolean _noMaterial = false;
	protected Material _material = new Material();
	
	public Mesh()
	{
		super();
	}
	
	public Mesh(Mesh mesh)
	{
		super();
		cloneFrom(mesh);
	}
	
	public boolean cloneFrom(Mesh mesh)
	{
		if(mesh.verticesBuffer != null && mesh.verticesBuffer.limit()>0)
		{
			float[] vertices = new float[mesh.verticesBuffer.limit()];
			mesh.verticesBuffer.position(0);
			mesh.verticesBuffer.get(vertices);
			mesh.verticesBuffer.position(0);
			setVertices(vertices);
		}
		
		if(mesh.normalesBuffer != null && mesh.normalesBuffer.limit()>0)
		{
			float[] verticeNormals = new float[mesh.normalesBuffer.limit()];
			mesh.normalesBuffer.position(0);
			mesh.normalesBuffer.get(verticeNormals);
			mesh.normalesBuffer.position(0);
			setVerticeNormales(verticeNormals);
		}
		
		if(mesh.colorBuffer != null && mesh.colorBuffer.limit()>0)
		{
			float[] verticeColors = new float[mesh.colorBuffer.limit()];
			mesh.colorBuffer.position(0);
			mesh.colorBuffer.get(verticeColors);
			mesh.colorBuffer.position(0);
			setVerticeColors(verticeColors);
		}
		
		numOfIndices = mesh.numOfIndices;
		if(mesh.indicesBuffer != null && mesh.indicesBuffer.limit()>0)
		{
			short[] indices = new short[mesh.indicesBuffer.limit()];
			mesh.indicesBuffer.position(0);
			mesh.indicesBuffer.get(indices);
			mesh.indicesBuffer.position(0);
			setIndices(indices);
		}
		
		rgba = mesh.rgba;
		
		_material.set(mesh._material);
		
		return true;
	}

    @Override
	protected void _prepareDraw(GL10 gl)
    {
		// Counter-clockwise winding.
		gl.glFrontFace(GL10.GL_CCW);
		// Enable face culling.
		gl.glEnable(GL10.GL_CULL_FACE);
		// What faces to remove with the face culling.
		gl.glCullFace(GL10.GL_BACK);
		
		// TODO add texture !
		// no texture
		gl.glDisable(GL10.GL_TEXTURE);
		
		// enable material for lighting
		if(!_noMaterial)
		{
			// TODO Bug ?
			//gl.glEnable(GL10.GL_COLOR_MATERIAL);
			_material.draw(gl);
		}

		// Enabled the vertices buffer for writing and to be used during
		// rendering.
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		// Specifies the location and data format of an array of vertex
		// coordinates to use when rendering.
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, verticesBuffer);
		if(normalesBuffer != null)
		{
			gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
			gl.glNormalPointer(GL10.GL_FLOAT, 0, normalesBuffer);
		}
		// Set flat color
		gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
		// Smooth color
		if (colorBuffer != null) {
			// Enable the color array buffer to be used during rendering.
			gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
			gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
		}

		super._prepareDraw(gl);
    }
    @Override
	protected void _drawElement(GL10 gl)
    {
		gl.glDrawElements(GL10.GL_TRIANGLES, numOfIndices,
				GL10.GL_UNSIGNED_SHORT, indicesBuffer);
    }
    @Override
	protected void _finishDraw(GL10 gl)
    {
		if (colorBuffer != null)
			gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
		if (normalesBuffer != null)
			gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisable(GL10.GL_COLOR_MATERIAL);
		// Disable face culling.
		gl.glDisable(GL10.GL_CULL_FACE);
		
		super._finishDraw(gl);
    }


	public void setVertices(float[] vertices) {
		// a float is 4 bytes, therefore we multiply the number if
		// vertices with 4.
		ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
		vbb.order(ByteOrder.nativeOrder());
		verticesBuffer = vbb.asFloatBuffer();
		verticesBuffer.put(vertices);
		verticesBuffer.position(0);
	}

	public void setVerticeNormales(float[] normales) {
		// a float is 4 bytes, therefore we multiply the number if
		// vertices with 4.
		ByteBuffer vbb = ByteBuffer.allocateDirect(normales.length * 4);
		vbb.order(ByteOrder.nativeOrder());
		normalesBuffer = vbb.asFloatBuffer();
		normalesBuffer.put(normales);
		normalesBuffer.position(0);
	}
	
	public void setVerticeColors(float[] colors) {
		// float has 4 bytes.
		ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
		cbb.order(ByteOrder.nativeOrder());
		colorBuffer = cbb.asFloatBuffer();
		colorBuffer.put(colors);
		colorBuffer.position(0);
	}

	public void setIndices(short[] indices) {
		// short is 2 bytes, therefore we multiply the number if
		// vertices with 2.
		ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
		ibb.order(ByteOrder.nativeOrder());
		indicesBuffer = ibb.asShortBuffer();
		indicesBuffer.put(indices);
		indicesBuffer.position(0);
		numOfIndices = indices.length;
	}

	public void setColor(float red, float green, float blue) {
		setColor(red, green, blue, 1);
	}

	public void setColor(float red, float green, float blue, float alpha) {
		// Setting the flat color.
		rgba[0] = red;
		rgba[1] = green;
		rgba[2] = blue;
		rgba[3] = alpha;
	}
	
	public Material getMaterial() {
		return _material;
	}
	
	public void translateVerticesBuffer(float tx, float ty, float tz)
	{
		float[] vertices = new float[verticesBuffer.limit()];
		verticesBuffer.position(0);
		verticesBuffer.get(vertices);
		for(int i=0; i<vertices.length/3; i+=3)
		{
			vertices[i  ] += tx; 
			vertices[i+1] += ty;
			vertices[i+2] += tz;
		}
		verticesBuffer.position(0);
		verticesBuffer.put(vertices);
		verticesBuffer.position(0);
	}
	protected void transformVerticesBuffer(float mf[])
	{
		float[] vertices = new float[verticesBuffer.limit()];
		float tvec[] = new float[8];
		verticesBuffer.position(0);
		verticesBuffer.get(vertices);
		for(int i=0; i<vertices.length/3; i+=3)
		{
			tvec[0]=vertices[i  ];
			tvec[1]=vertices[i+1];
			tvec[2]=vertices[i+2];
			tvec[3]=1;
			Matrix.multiplyMV(tvec, 4, mf, 0, tvec, 0);
			vertices[i  ] = tvec[4]; 
			vertices[i+1] = tvec[5];
			vertices[i+2] = tvec[6];
		}
		verticesBuffer.position(0);
		verticesBuffer.put(vertices);
		verticesBuffer.position(0);
	}
	
	public void generateNormales()
	{
		float[] vertices = new float[verticesBuffer.limit()];
		short[] indices = new short[indicesBuffer.limit()];
		verticesBuffer.position(0);
		verticesBuffer.get(vertices);
		verticesBuffer.position(0);
		indicesBuffer.position(0);
		indicesBuffer.get(indices);
		indicesBuffer.position(0);
		
		Vector3D n = new Vector3D(0, 0, 0);
		Vector3D v0 = new Vector3D(0, 0, 0);
		Vector3D v1 = new Vector3D(0, 0, 0);
		Vector3D v2 = new Vector3D(0, 0, 0);
		
		float[] normales = new float[verticesBuffer.limit()];		
		for(int v=0; v<vertices.length; v+=3)
		{
			n.set(0f, 0f, 0f);
			
			short iv = (short)(v/3);
			for(int t=0; t<indices.length; t+=3)
			{
				if(indices[t+0] == iv || indices[t+1] == iv || indices[t+2] == iv)
				{
					int id0 = indices[t+0]*3;
					int id1 = indices[t+1]*3;
					int id2 = indices[t+2]*3;
					
					v0.set(vertices[id0], vertices[id0+1], vertices[id0+2]);
					v1.set(vertices[id1], vertices[id1+1], vertices[id1+2]);
					v2.set(vertices[id2], vertices[id2+1], vertices[id2+2]);
					
					// build normal
					Vector3D nt = Vector3D.build_tri_normal(v0, v1, v2);

					n.x += nt.x;
					n.y += nt.y;
					n.z += nt.z;
				}
			}
			
			float l=FloatMath.sqrt((n.x*n.x) + (n.y*n.y) + (n.z*n.z));
			if(l>0)
			{
				n.x/=l;
				n.y/=l;
				n.z/=l;
			}

			normales[v  ] = n.x; 
			normales[v+1] = n.y;
			normales[v+2] = n.z;	
		}
		
		setVerticeNormales(normales);
	}
}
